<!DOCTYPE html>
<html lang="zh-cn"><head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    
    <link href="/css/bootstrap.min.css" rel="stylesheet">
    <link href="/css/main.css" rel="stylesheet">

    <link rel="shortcut icon" type="image/x-icon" href="/img/favicon.ico" />

    <title>Golang生成随机字符串 | wiseAI的小站</title>
</head><body><div class="container-fluid">
    <nav class="navbar fixed-top navbar-expand-sm navbar-dark bg-dark">
        <div class="container-fluid">
            <a class="navbar-brand" href="/">
                <img src="/img/favicon.ico" alt="" width="30" height="24" class="d-inline-block align-text-top">
                wiseAI的小站
            </a>
            <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
                <span class="navbar-toggler-icon"></span>
            </button>
            <div class="collapse navbar-collapse" id="navbarSupportedContent">
                <ul class="navbar-nav me-auto mb-2 mb-lg-0">
                    
                    <li class="nav-item">
                        <a  class="nav-link"   href="/articles/">文章</a>
                    </li>
                    
                    <li class="nav-item">
                        <a  class="nav-link"   href="/categories/">分类</a>
                    </li>
                    
                    <li class="nav-item">
                        <a  class="nav-link"   href="/tags/">关键字</a>
                    </li>
                    
                    <li class="nav-item">
                        <a  class="nav-link"   href="/post/golang/">golang编程</a>
                    </li>
                    
                    <li class="nav-item">
                        <a  class="nav-link"   href="/about/">关于</a>
                    </li>
                    
                </ul>
                <form class="d-flex">
                    <input id="search-query" class="form-control me-2" type="search" placeholder="Search for anything..." aria-label="Search">
                </form>
            </div>
        </div>
    </nav>
</div>




<div id="content">
<div class="container article">
	<h1>Golang生成随机字符串</h1>
	<div class="post-meta">
		<div>
			
			
			By wiseai | 2022-07-06 | 4 minutes
		</div>
		<div class="tags">
			
			<a class="btn btn-light links btn-sm" href="/tags/%E9%9A%8F%E6%9C%BA/">随机</a>
			
			<a class="btn btn-light links btn-sm" href="/tags/%E7%BC%96%E7%A8%8B/">编程</a>
			
			<a class="btn btn-light links btn-sm" href="/tags/golang/">golang</a>
			
		</div>
	</div>
	<div>
		<div class="article-post">
			<p>假如我们要生成一个固定长度的随机字符串，包含大小写字母，没有数字，没有特殊字符串，那么我们怎么做呢？需要怎样优化，才会更简单，更高效？在最终的方案之前，我们看看最常见的写法是怎样的，然后是如何一步步演进到最终的高效率方案的。好吧，先看下最原始的方案。</p>
<ul>
<li>常见做法(Runes)</li>
</ul>
<pre tabindex="0"><code>func init() {
	rand.Seed(time.Now().UnixNano())
}

var letterRunes = []rune(&#34;abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ&#34;)

func RandStringRunes(n int) string {
	b := make([]rune, n)
	for i := range b {
		b[i] = letterRunes[rand.Intn(len(letterRunes))]
	}
	return string(b)
}
</code></pre><p>这个实现比较简单，二十六字母（大小写），然后随机取数，获得随机字符串。</p>
<ul>
<li>Bytes改进</li>
</ul>
<p>我们在最开始的时候进行了假设，我们的随机字符串只包含大小写字母，这样的话，我们发现没有必要使用rune类型存储，因为在Golang（Go语言）UTF-8编码下，英文字母和byte字节是一对一的。byte的本质是uint8类型，而rune本质是int32类型。我们改进后的代码如下：</p>
<pre tabindex="0"><code>const letterBytes = &#34;abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ&#34;

func RandStringBytes(n int) string {
	b := make([]byte, n)
	for i := range b {
		b[i] = letterBytes[rand.Intn(len(letterBytes))]
	}
	return string(b)
}
</code></pre><p>仔细看上面的代码，我们不光对rune类型进行了改进，还把原来的letter变量变成了常量，这样len(letterBytes)也是一个常量，代码的效率将大大提升。</p>
<ul>
<li>余数改进</li>
</ul>
<p>我们前面的方案都是通过调用rand.Intn()生成的随机字符，这个rand.Intn()其实是委托调用的Rand.Intn(),而Rand.Intn()最终又是调用的Rand.Int31n()实现。相比我们直接调用rand.Int63()来说，rand.Intn()要慢很多。</p>
<p>所以我们可以把rand.Intn()换成rand.Int63()来提高效率，为了不超过letterBytes的索引范围，我们使用余数来保证。</p>
<pre tabindex="0"><code>func RandStringBytesRmndr(n int) string {
	b := make([]byte, n)
	for i := range b {
		b[i] = letterBytes[rand.Int63() % int64(len(letterBytes))]
	}
	return string(b)
}
</code></pre><p>这种方式虽然快，但是有个缺点，就是每个字母的概率可能会不一样，不过52个字母相比1&laquo;63-1是在太小太小，所以在这种情况下，这个缺点可以忽略不计。</p>
<ul>
<li>Masking 掩码</li>
</ul>
<p>基于前面的方案，我们可以进一步改进，使用随机数的最低位保证字母的均等分配，也就是掩码的方式。我们现在有52个字母，52用二进制表示就是52==110100b，所以我们可以只使用rand.Int63()返回最低的6位数就可以。为了保证平均分配，如果返回的只大于len(letterBytes)-1，则舍弃不用。</p>
<pre tabindex="0"><code>const letterBytes = &#34;abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ&#34;
const (
	letterIdxBits = 6 // 6 bits to represent a letter index
	letterIdxMask = 1&lt;&lt;letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
	)

	func RandStringBytesMask(n int) string {
		b := make([]byte, n)
		for i := 0; i &lt; n; {
			if idx := int(rand.Int63() &amp; letterIdxMask); idx &lt; len(letterBytes) {
				b[i] = letterBytes[idx]
				i++
			}
		}
		return string(b)
	}
</code></pre><p>按照作者的推测，在52个字母的情况下，随机到超过范围的可能性(64-52)/64 = 0.19,按上面的代码，如果超过范围会重复生成，重复的10次的概率仅有1e-8。</p>
<ul>
<li>Masking 掩码改进</li>
</ul>
<p>上一步的方案，我们使用rand.Int63()可以生成63个随机位的数，但是我们只用了最低位的6个，有点浪费，因为获取随机数是我们整个代码中最慢的部分。现在我们有52个字母，意味着6位编码字母索引即可满足，所以我们使用rand.Int63()生成的随机数可以被我们使用63/6=10次。</p>
<pre tabindex="0"><code>const letterBytes = &#34;abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ&#34;
const (
	letterIdxBits = 6 // 6 bits to represent a letter index
	letterIdxMask = 1&lt;&lt;letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
	letterIdxMax = 63 / letterIdxBits // # of letter indices fitting in 63 bits
	)

	func RandStringBytesMaskImpr(n int) string {
		b := make([]byte, n)
		// A rand.Int63() generates 63 random bits, enough for letterIdxMax letters!
		for i, cache, remain := n-1, rand.Int63(), letterIdxMax; i &gt;= 0; {
			if remain == 0 {
				cache, remain = rand.Int63(), letterIdxMax
			}
			if idx := int(cache &amp; letterIdxMask); idx &lt; len(letterBytes) {
				b[i] = letterBytes[idx]
				i--
			}
			cache &gt;&gt;= letterIdxBits
			remain--
		}

		return string(b)
	}
</code></pre><p>把生成的63位的随机数，分成10部分，每一部分都可以被我们使用，这样我们调用rand.Int63()次数将大大降低，进而提升效率。</p>
<ul>
<li>rand Source 优化</li>
</ul>
<p>rand.Rand其实是使用了一个rand.Source作为生成随机数的源，这个rand.Source是个接口，正好有个func Int63() int64 方法。</p>
<pre tabindex="0"><code>// A Source represents a source of uniformly-distributed
// pseudo-random int64 values in the range [0, 1&lt;&lt;63).
type Source interface {
Int63() int64
Seed(seed int64)
}
</code></pre><p>这正好是我们需要的，也够我们用了。改进后代码如下：</p>
<pre tabindex="0"><code>var src = rand.NewSource(time.Now().UnixNano())

func RandStringBytesMaskImprSrc(n int) string {
	b := make([]byte, n)
	// A src.Int63() generates 63 random bits, enough for letterIdxMax characters!
	for i, cache, remain := n-1, src.Int63(), letterIdxMax; i &gt;= 0; {
		if remain == 0 {
			cache, remain = src.Int63(), letterIdxMax
		}
		if idx := int(cache &amp; letterIdxMask); idx &lt; len(letterBytes) {
			b[i] = letterBytes[idx]
			i--
		}
		cache &gt;&gt;= letterIdxBits
		remain--
	}

	return string(b)
}
</code></pre><p>原来的rand.Int63()是整个rand包全局的，而且支持安全高并发，所以速度比较慢。现在我们自己创建的这个src只有我们自己用，所以效率比较高。
strings.Builder 改进</p>
<p>这个是G0 1.10 新增的功能，提升字符串拼接的效率，这方面的可以参考我以前写的三篇文章，这里不做过多的介绍了。</p>
<p><a href="https://mp.weixin.qq.com/s?__biz=MzI3MjU4Njk3Ng%3D%3D&amp;chksm=eb3103e0dc468af626e41f136b4652a3fb24527a69db8689c9f691ac94a12a5b6dd6df7b39d3&amp;idx=1&amp;mid=2247484015&amp;scene=21&amp;sn=4fe47b59e7c1595f4d6723c876910247#wechat_redirect">Go语言字符串高效拼接（一）</a></p>
<p><a href="https://mp.weixin.qq.com/s?__biz=MzI3MjU4Njk3Ng%3D%3D&amp;chksm=eb31030edc468a18ffce72f8358f5fa3d4eaa05170569cde6615f86afea85e673767fd049fbc&amp;idx=1&amp;mid=2247484033&amp;scene=21&amp;sn=909064f18cb624ff3c2a061f4e3994f4#wechat_redirect">Go语言字符串高效拼接（二）</a></p>
<p><a href="https://mp.weixin.qq.com/s?__biz=MzI3MjU4Njk3Ng%3D%3D&amp;chksm=eb31030adc468a1cf319086310111998a031691334784fd7afdca2afae0eec7656e574d4e89e&amp;idx=1&amp;mid=2247484037&amp;scene=21&amp;sn=653dcecc028b97e16eed393f37a925d2#wechat_redirect">Go语言字符串高效拼接（三）</a></p>
<p>经过改进后，代码如下：</p>
<pre tabindex="0"><code>func RandStringBytesMaskImprSrcSB(n int) string {
	sb := strings.Builder{}
	sb.Grow(n)
	// A src.Int63() generates 63 random bits, enough for letterIdxMax characters!
	for i, cache, remain := n-1, src.Int63(), letterIdxMax; i &gt;= 0; {
		if remain == 0 {
			cache, remain = src.Int63(), letterIdxMax
		}
		if idx := int(cache &amp; letterIdxMask); idx &lt; len(letterBytes) {
			sb.WriteByte(letterBytes[idx])
			i--
		}
		cache &gt;&gt;= letterIdxBits
		remain--
	}

	return sb.String()
}
</code></pre><p>使用unsafe包模拟 strings.Builder</p>
<p>strings.Builder的原理其实很简单，是内置了一个[]byte存储字符，最终转换为string的时候为了避免拷贝，使用了unsafe包。</p>
<pre tabindex="0"><code>// String returns the accumulated string.
func (b *Builder) String() string {
	return *(*string)(unsafe.Pointer(&amp;b.buf))
}
</code></pre><p>以上这些我们可以自己来做，看看我们重写后的代码。</p>
<pre tabindex="0"><code>func RandStringBytesMaskImprSrcUnsafe(n int) string {
	b := make([]byte, n)
	// A src.Int63() generates 63 random bits, enough for letterIdxMax characters!
	for i, cache, remain := n-1, src.Int63(), letterIdxMax; i &gt;= 0; {
		if remain == 0 {
			cache, remain = src.Int63(), letterIdxMax
		}
		if idx := int(cache &amp; letterIdxMask); idx &lt; len(letterBytes) {
			b[i] = letterBytes[idx]
			i--
		}
		cache &gt;&gt;= letterIdxBits
		remain--
	}

	return *(*string)(unsafe.Pointer(&amp;b))
}
</code></pre><p>效果和使用strings.Builder一样，而且看起来更简洁了。</p>
<ul>
<li>Benchmark 性能测试</li>
</ul>
<p>以后，我们通过一步步的改进代码，提升效率，现在我们通过Benchmark测试看下这些方法的效果。</p>
<pre tabindex="0"><code>BenchmarkRunes-4 2000000 723 ns/op 96 B/op 2 allocs/op
BenchmarkBytes-4 3000000 550 ns/op 32 B/op 2 allocs/op
BenchmarkBytesRmndr-4 3000000 438 ns/op 32 B/op 2 allocs/op
BenchmarkBytesMask-4 3000000 534 ns/op 32 B/op 2 allocs/op
BenchmarkBytesMaskImpr-4 10000000 176 ns/op 32 B/op 2 allocs/op
BenchmarkBytesMaskImprSrc-4 10000000 139 ns/op 32 B/op 2 allocs/op
BenchmarkBytesMaskImprSrcSB-4 10000000 134 ns/op 16 B/op 1 allocs/op
BenchmarkBytesMaskImprSrcUnsafe-4 10000000 115 ns/op 16 B/op 1 allocs/op
</code></pre><p>仅仅从rune到byte的改进，我们就获得了<strong>24%的提升，内存占用降低了三分之一</strong> 。</p>
<p>使用rand.Int63替换掉原来的rand.Intn，我们又获得了近20%的提升。</p>
<p>单纯的使用掩码，因为重复获取可用索引的问题，性能下降了 <strong>-22%</strong>。</p>
<p>但是当我们对 Masking 掩码 进行改进，分为10部分缓存的时候，我们获得了3倍的提升。</p>
<p>使用rand.Source 代替 rand.Rand, 我们再次获得了21%的提升。</p>
<p>使用strings.Builder,速度提升虽然只有3.5%,但是内存分配降低了50% 。</p>
<p>最后，通过unsafe包精简重写了strings.Builder的功能，我们又获得了14%的提升。</p>
<p>最终，RandStringBytesMaskImprSrcUnsafe比RandStringRunes快6.3倍，并且只使用了六分之一的内存和一半的内存分配，我们就完成了任务。</p>
<ul>
<li>结束语</li>
</ul>
<p>这是一篇stackoverflow的文章，有人提问 How to generate a random string of a fixed length in Go? ，icza 做了非常精彩的回答，我把整个翻译下来加以整理分享给大家。</p>
<p>这是一篇非常棒的文章，它的意义不光是回答这个问题，还有帮助我们建立如何一步步优化的思路以及追求极致的极客精神。</p>
<p>原文链接：<a href="https://blog.csdn.net/flysnow_org/java/article/details/103520891">https://blog.csdn.net/flysnow_org/java/article/details/103520891</a></p>
<p>博客链接：<a href="https://www.flysnow.org/archives/">https://www.flysnow.org/archives/</a></p>

		</div>
	</div>
</div>


<div class="container">
    
    <div class="row">
        
        <div class="col-5">
            <a class="page-link link-dark text-end dh" href="/post/u%E7%9B%98%E5%90%AF%E5%8A%A8%E7%9B%98%E5%88%B6%E4%BD%9C/">
                <h5>前一篇</h5><br>
                U盘启动盘制作
            </a>            
        </div>
        
        <div class="col-2">
        </div>
        
        <div class="col-5">
            <a class="page-link link-dark text-start dh" href="/post/ssh%E5%85%8D%E5%AF%86%E7%A0%81%E7%99%BB%E9%99%86/">
                <h5>后一篇</h5><br>
                ssh免密码登陆
            </a>            
        </div>
        
    </div>
    
</div>

        </div><br><br>
<footer class="container">
    <h2>友情链接</h2>
    <hr>
    <nav class="nav nav-pills flex-column flex-sm-row">
        
        <a class="flex-sm-fill text-sm-center nav-link links link-dark" href="https://wiseai.gitee.io/pages/gnzg/index.html" target="_blank">孤鸟之歌</a>
        
        <a class="flex-sm-fill text-sm-center nav-link links link-dark" href="https://wiseai.gitee.io/pages/mm/index.html" target="_blank">生成随机字符</a>
        
        <a class="flex-sm-fill text-sm-center nav-link links link-dark" href="https://wiseai.gitee.io/pages/md/index.html" target="_blank">MarkDown</a>
        
        <a class="flex-sm-fill text-sm-center nav-link links link-dark" href="https://gitee.com/" target="_blank">Gitee</a>
        
        <a class="flex-sm-fill text-sm-center nav-link links link-dark" href="https://github.com/" target="_blank">Github</a>
        
        <a class="flex-sm-fill text-sm-center nav-link links link-dark" href="https://www.aliyun.com/" target="_blank">阿里云</a>
        
        <a class="flex-sm-fill text-sm-center nav-link links link-dark" href="https://cloud.tencent.com/" target="_blank">腾讯云</a>
        
        <a class="flex-sm-fill text-sm-center nav-link links link-dark" href="https://www.oschina.net/" target="_blank">OSCHINA开源中国</a>
        
        <a class="flex-sm-fill text-sm-center nav-link links link-dark" href="https://gitee.com/wiseai/the-way-to-go_ZH_CN/blob/master/eBook/directory.md" target="_blank">the way to go</a>
        
        <a class="flex-sm-fill text-sm-center nav-link links link-dark" href="https://topgoer.com/" target="_blank">golang文档</a>
        
        <a class="flex-sm-fill text-sm-center nav-link links link-dark" href="https://goframe.org/display/gf" target="_blank">GoFrame</a>
        
        <a class="flex-sm-fill text-sm-center nav-link links link-dark" href="https://www.aliyundrive.com/" target="_blank">阿里云盘</a>
        
        <a class="flex-sm-fill text-sm-center nav-link links link-dark" href="https://cn.vuejs.org/" target="_blank">vue3文档</a>
        
        <a class="flex-sm-fill text-sm-center nav-link links link-dark" href="https://element-plus.gitee.io/zh-CN/" target="_blank">element-plus文档</a>
        
        <a class="flex-sm-fill text-sm-center nav-link links link-dark" href="https://www.runoob.com/vue3/vue3-tutorial.html" target="_blank">vue3菜鸟教程</a>
        
        <a class="flex-sm-fill text-sm-center nav-link links link-dark" href="https://v5.bootcss.com/" target="_blank">bootstrap v5 中文文档</a>
        
        <a class="flex-sm-fill text-sm-center nav-link links link-dark" href="https://www.bootstrap.cn/" target="_blank">bootstrap文档</a>
        
        <a class="flex-sm-fill text-sm-center nav-link links link-dark" href="https://caddy2.dengxiaolong.com/docs/" target="_blank">caddy中文教程</a>
        
        <a class="flex-sm-fill text-sm-center nav-link links link-dark" href="https://www.58pic.com/" target="_blank">千图</a>
        
        <a class="flex-sm-fill text-sm-center nav-link links link-dark" href="https://ifonts.com/" target="_blank">iFonts</a>
        
        <a class="flex-sm-fill text-sm-center nav-link links link-dark" href="https://marketing.qiniu.com/" target="_blank">七牛云</a>
        
        <a class="flex-sm-fill text-sm-center nav-link links link-dark" href="https://www.ixigua.com/" target="_blank">西瓜视频</a>
        
        <a class="flex-sm-fill text-sm-center nav-link links link-dark" href="https://wiseai.gitee.io/pages/yugang/index.html" target="_blank">鱼缸计算</a>
        
        <a class="flex-sm-fill text-sm-center nav-link links link-dark" href="https://justcc.mengkang.net/#/" target="_blank">C语言JustCC</a>
        
        <a class="flex-sm-fill text-sm-center nav-link links link-dark" href="https://wiseai.gitee.io/pages/pptist/" target="_blank">PPTist</a>
        
        <a class="flex-sm-fill text-sm-center nav-link links link-dark" href="/index.xml" target="_blank">RSS</a>
        
        <a class="flex-sm-fill text-sm-center nav-link links link-dark" href="https://www.kancloud.cn/idcpj/python/418553" target="_blank">参考资料</a>
        
    </nav>

    <div class="copyright text-center">
      <span class="power-by">
        Powered by <a class="links" href="https://gohugo.io" target="_blank">Hugo</a>
    </span>
    <span>|</span>
    <span>
        Theme - <a class="links" href="https://github.com/wiseai-go/blog-hugo" target="_blank">WiseAI</a>
    </span>
    <br>
    <span class="copyright-year">
        &copy;
        
        2017 -
        2023<span>
            陇ICP备15000157号
            
        </span></span>

</div>
</footer>
<script src="/js/bootstrap.bundle.min.js"></script>


</body>
</html>
